import { defineStore } from 'pinia'
import { computed, watchEffect } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { createEventHook, useLocalStorage } from '@vueuse/core'

import { parseJWT } from '@/utils/jwt'
import { LocalStorage } from '@/constants/localStorage'
import { ApiRefreshToken, UserCustomCode } from '@/apis/user'
import { buildRedirectLoginLocation } from '@/router/redirect-login'

export const useTokenStore = defineStore('token', () => {
  const accessToken = useLocalStorage(LocalStorage.Token, '')
  const accessTokenExpire = computed<number>(() => {
    return accessToken.value === '' ? 0 : parseJWT(accessToken.value).exp * 1000
  })
  const refreshToken = useLocalStorage(LocalStorage.RefreshToken, '')
  const hasRefreshToken = computed<boolean>(() => {
    return refreshToken.value !== ''
  })

  const setAccessToken = (token: string) => {
    localStorage.setItem(LocalStorage.Token, token)
    accessToken.value = token
  }

  const setToken = (access: string, refresh: string) => {
    setAccessToken(access)
    localStorage.setItem(LocalStorage.RefreshToken, refresh)
    refreshToken.value = refresh
  }

  const route = useRoute()
  const router = useRouter()
  const afterRefreshAccessToken = createEventHook<string>()
  const refreshAccessToken = async () => {
    const data = await ApiRefreshToken(refreshToken.value).catch(() => ({
      code: 0,
      data: null
    }))

    if (data.code !== UserCustomCode.SuccessRefresh) {
      afterRefreshAccessToken.trigger('')
      await router.push(buildRedirectLoginLocation(route))
    } else {
      const { accessToken } = data.data!
      setAccessToken(accessToken)
      afterRefreshAccessToken.trigger(accessToken)
    }
  }

  /** @desc access token 预留时间(s), 若剩余有效时间小于该时间则立即刷新 token */
  const reserveTime = 60
  let initiated: (v?: unknown) => void
  const initiatePromise = new Promise((res) => (initiated = res))
  watchEffect(async (clean) => {
    if (!hasRefreshToken.value) {
      initiated()
      return
    }

    if (accessTokenExpire.value < Date.now() + reserveTime * 1000) {
      await refreshAccessToken()
    }

    const refreshTimer = setTimeout(
      refreshAccessToken,
      accessTokenExpire.value - Date.now() - reserveTime
    )
    clean(() => clearTimeout(refreshTimer))

    initiated()
  })

  return {
    initiatePromise,
    hasRefreshToken,

    setToken,
    afterRefreshAccessToken: afterRefreshAccessToken.on
  }
})
